package infrastructure;


import domain.annotations.Key;
import domain.annotations.NoMapper;
import domain.annotations.TableName;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 仓储抽象
 * 用于对实体进行映射和管理
 *
 * @author wyl
 */
public abstract class Repository<TEntity> {

    private final String addSql;
    private final String deleteByIdSql;
    private final String updateSql;
    private final String queryAllSql;
    private final String queryByIdSql;

    private final Class<?> entityClass;
    private final String tableName;
    private final String identifierName;

    public Repository() throws Exception {
        ParameterizedType t = (ParameterizedType) (this.getClass().getGenericSuperclass());
        Class<?> typeArgument0 = (Class<TEntity>) (t.getActualTypeArguments()[0]);
        this.entityClass = typeArgument0;

        //实体所在的表名
        this.tableName = getRequireTableName();

        //实体标识符(主键)
        this.identifierName = getRequireIdentifierName();

        this.addSql = SqlBuilder.buildAddSqlTemplate(typeArgument0, tableName);
        System.out.println(addSql);

        this.deleteByIdSql = SqlBuilder.buildDeleteByIdSqlTemplate(tableName, identifierName);
        System.out.println(deleteByIdSql);

        this.updateSql = SqlBuilder.buildUpdateSqlTemplate(typeArgument0, tableName, identifierName);
        System.out.println(updateSql);

        this.queryAllSql = SqlBuilder.buildQueryAllSqlTemplate(typeArgument0, tableName);
        System.out.println(queryAllSql);

        this.queryByIdSql = SqlBuilder.buildQueryByIdTemplate(typeArgument0, tableName, identifierName);
        System.out.println(queryByIdSql);

    }


    /**
     * 查询全部
     *
     * @return 返回此类型实体的全部集合
     */
    public Collection<TEntity> queryAll() {
        Connection conn = null;
        try {
            conn = SqliteConnectionPool.getConn();
            ResultSet rs = conn.createStatement().executeQuery(queryAllSql);
            List<TEntity> list = new ArrayList<>();
            while (rs.next()) {
                list.add(createEntity(rs));
            }
            return list;
        } catch (Exception e) {
            System.out.println("查询过程出现了异常");
            e.printStackTrace();
        } finally {
            closeDbConn(conn);
        }

        return null;
    }


    /**
     * 查询实体按照标识符（主键,具有唯一性的键）
     *
     * @param id 标识符
     * @return 当前id的实体
     */
    public TEntity queryById(Object id) {
        Connection conn = null;
        TEntity entity = null;
        try {
            conn = SqliteConnectionPool.getConn();
            PreparedStatement p = conn.prepareStatement(queryByIdSql);
            setPreparedStatement(1, id, p);
            ResultSet rs = p.executeQuery();
            if (rs.next()) {
                entity = createEntity(rs);
            }
        } catch (Exception e) {
            System.out.println("查询过程出现了异常");
            e.printStackTrace();
        } finally {
            closeDbConn(conn);
        }
        return entity;
    }


    /**
     * 按某个字段查询
     *
     * @param propName  字段名
     * @param propValue 字段值
     * @return 查询实体结构集合
     */
    public Collection<TEntity> queryByProp(String propName, Object propValue) {
        Connection conn = null;
        try {
            conn = SqliteConnectionPool.getConn();
            String sqlTemplate = SqlBuilder.buildQueryByPropNameTemplate(entityClass, tableName, propName);
            PreparedStatement p = conn.prepareStatement(sqlTemplate);
            setPreparedStatement(1, propValue, p);
            ResultSet rs = p.executeQuery();
            List<TEntity> entities = new ArrayList<>();
            while (rs.next()) {
                entities.add(createEntity(rs));
            }
            return entities;
        } catch (Exception e) {
            System.out.println("查询过程出现了异常");
            e.printStackTrace();
        } finally {
            closeDbConn(conn);
        }

        return null;
    }


    /**
     * 向数据库中添加一个实体
     *
     * @param d 需要添加的实体
     * @return 添加结果
     */
    public boolean add(TEntity d) {
        Connection conn = null;
        try {
            conn = SqliteConnectionPool.getConn();
            PreparedStatement p = conn.prepareStatement(addSql);

            Field[] fields = entityClass.getDeclaredFields();
            int paraIndex = 1;
            for (int i = 0; i < fields.length; i++) {
                Field f = fields[i];
                if (f.isAnnotationPresent(NoMapper.class)) {
                    continue;
                }
                f.setAccessible(true);
                setPreparedStatement(paraIndex++, f.get(d), p);
            }

            return p.executeUpdate() > 0;
        } catch (Exception e) {
            System.out.println("添加实体出现异常");
            e.printStackTrace();
        } finally {
            closeDbConn(conn);
        }

        return false;
    }


    /**
     * 更新实体
     * 利用实体标识符进行实体的索引
     *
     * @param d 需要更新的实体
     * @return 更新结果
     */
    public boolean update(TEntity d) {
        Connection conn = null;
        try {
            conn = SqliteConnectionPool.getConn();
            PreparedStatement p = conn.prepareStatement(updateSql);
            Field[] fields = entityClass.getDeclaredFields();
            //设置PreparedStatement
            int paraIndex = 1;
            Field identifierField = null;
            for (int i = 0; i < fields.length; i++) {
                Field f = fields[i];
                if (f.isAnnotationPresent(NoMapper.class)) {
                    continue;
                }
                f.setAccessible(true);
                //设置identifierName的值
                if (identifierName.equals(f.getName())) {
                    identifierField = f;
                    continue;
                }
                setPreparedStatement(paraIndex++, f.get(d), p);
            }
            //设置条件的值，为最后一个占位符
            setPreparedStatement(paraIndex, identifierField.get(d), p);
            //设置PreparedStatement 完成
            return p.executeUpdate() > 0;
        } catch (Exception e) {
            System.out.println("更新实体出现异常");
            e.printStackTrace();
        } finally {
            closeDbConn(conn);
        }

        return false;
    }


    /**
     * 删除实体按照标识符
     *
     * @param id 实体标识符
     * @return 返回删除结果
     */
    public boolean deleteById(Object id) {
        Connection conn = null;
        try {
            conn = SqliteConnectionPool.getConn();
            PreparedStatement p = conn.prepareStatement(deleteByIdSql);
            setPreparedStatement(1, id, p);
            return p.executeUpdate() > 0;
        } catch (Exception e) {
            System.out.println("删除实体出现异常");
            e.printStackTrace();
        } finally {
            closeDbConn(conn);
        }
        return false;
    }


    /**
     * 对占位符赋值
     *
     * @param index 占位符索引
     * @param o     值
     * @param p     PreparedStatement对象
     */
    private void setPreparedStatement(int index, Object o, PreparedStatement p) throws Exception {
        if (o == null) {

        } else if (o instanceof String) {
            p.setString(index, (String) o);
        } else if (o instanceof Short) {
            p.setShort(index, (Short) o);
        } else if (o instanceof Integer) {
            p.setInt(index, (Integer) o);
        } else if (o instanceof Long) {
            p.setLong(index, (Long) o);
        } else if (o instanceof Float) {
            p.setFloat(index, (Float) o);
        } else if (o instanceof Double) {
            p.setDouble(index, (Double) o);
        } else if (o instanceof BigDecimal) {
            p.setBigDecimal(index, (BigDecimal) o);
        } else if (o instanceof Boolean) {
            p.setBoolean(index, (Boolean) o);
        } else if (o instanceof Date) {
            p.setDate(index, (Date) o);
        } else if (o instanceof Timestamp) {
            p.setTimestamp(index, (Timestamp) o);
        } else if (o instanceof Time) {
            p.setTime(index, (Time) o);
        } else {
            throw new Exception("没有" + o.getClass() + "的转换");
        }

    }

    /**
     * 利用结果集创造一个实体对象
     * 会跳过标记@NoMapper的字段
     *
     * @param rs 数据库操作结果集合
     */
    private TEntity createEntity(ResultSet rs) throws Exception {
        try {
            //调用私有的构造方法
            Constructor<?> constructor = entityClass.getDeclaredConstructor();
            constructor.setAccessible(true);
            TEntity entity = (TEntity) constructor.newInstance();
            Field[] fields = entityClass.getDeclaredFields();

            for (int i = 0; i < fields.length; i++) {
                Field f = fields[i];
                if (f.isAnnotationPresent(NoMapper.class)) {
                    continue;
                }
                f.setAccessible(true);
                Object o = rs.getObject(f.getName());
                //处理Integer类型转为Long
                if (o instanceof Integer) {
                    f.set(entity, Long.valueOf((Integer) o));
                    continue;
                }
                f.set(entity, o);
            }

            return entity;
        } catch (Exception e) {
            System.out.println("创建实体失败");
            throw e;
        }
    }

    /**
     * 获取实体所在的表名称
     *
     * @return 实体对应的表名
     */
    protected String getRequireTableName() {
        String tableName = null;
        //存在注解按照注解的命名，不存在则按照类名
        TableName tableNameAnnotation = this.entityClass.getAnnotation(TableName.class);
        if (tableNameAnnotation != null) {
            tableName = tableNameAnnotation.value();
        } else {
            tableName = this.entityClass.getSimpleName();
        }
        return tableName;
    }

    /**
     * 获取实体的标识符名称
     *
     * @return 唯一标识符名称
     * @throws Exception 获取不到标识符字段名称
     */
    protected String getRequireIdentifierName() throws Exception {

        String identifierName = null;
        //获取@Key标注的字段如果有的话,则改变标识符名称
        String keyName = getKeyName();
        if (keyName != null) {
            identifierName = keyName;
        }
        //若@Key注解获取不到主键的情况下，按照约定以属性名为id的属性为主键
        if (identifierName == null && hasIdProperty()) {
            identifierName = "id";
        }
        //主键不存在，抛出异常
        if (identifierName == null) {
            throw new Exception("请为实体类型" + entityClass.getName() + "设置主键，以id命名或者标记@Key注解");
        }
        return identifierName;
    }

    /**
     * 是否存在名为id的属性字段
     *
     * @return ture存在
     */
    private boolean hasIdProperty() {
        return Arrays.stream(entityClass.getDeclaredFields()).anyMatch(p -> "id".equals(p.getName()));
    }

    /**
     * @return 获取当前实体类型标记@Key注解的字段名
     * 如无标记则返回null
     * @throws Exception 不允许标记多个@Key
     */
    private String getKeyName() throws Exception {
        String keyName = null;
        List<Field> keyFields = Arrays.stream(entityClass.getDeclaredFields())
                .filter(f -> f.isAnnotationPresent(Key.class))
                .collect(Collectors.toList());

        //不允许标记多个@Key注解
        if (keyFields.size() > 1) {
            throw new Exception("同一实体最多标记一个@Key");
        }
        //存在一个标注注解的字段
        if (keyFields.size() == 1) {
            keyName = keyFields.get(0).getName();
        }

        return keyName;
    }

    /**
     * 释放数据库链接
     *
     * @param conn 数据库链接对象
     */
    private void closeDbConn(Connection conn) {
        try {
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            System.out.println("关闭数据库链接出现异常");
            e.printStackTrace();
        }
    }

}
